#pragma once

#include <vector>
#include <stdint.h>
#include <string>

class Tensor
{
public:
    Tensor() = default;
    virtual ~Tensor() = default;

    static std::vector<std::vector<double>> dot(const std::vector<std::vector<double>> &a, const std::vector<std::vector<double>> &b);
    static std::vector<double> dot(const std::vector<std::vector<double>> &a, const std::vector<double> &b);
    static std::vector<std::vector<double>> add(const std::vector<std::vector<double>> &a, const std::vector<std::vector<double>> &b);
    static std::vector<std::vector<double>> transpose(const std::vector<std::vector<double>> &a);
    static double sum(const std::vector<double> &a) noexcept;
    static std::vector<double> sum(const std::vector<std::vector<double>> &a, const int64_t dim = 0);
    static std::vector<std::vector<double>> reshape(const std::vector<double> &input, int64_t n, int64_t m);
    static std::vector<std::vector<double>> multiply(const std::vector<std::vector<double>> &a, const std::vector<std::vector<double>> &b);
    static std::vector<std::vector<double>> multiply(const std::vector<std::vector<double>> &a, double b);
    static std::vector<double> multiply(const std::vector<double> &a, double b);
    static std::vector<std::vector<double>> minus(const std::vector<std::vector<double>> &a, const std::vector<std::vector<double>> &b);
    static std::vector<double> minus(const std::vector<double> &a, const std::vector<double> &b);
    
    template<typename T>
    static std::string shape(const std::vector<std::vector<T>> &data)
    {
        return std::string{"("} + std::to_string(data.size()) + std::string{", "} +
        std::to_string(data[0].size()) + ")";
    }

    template<typename input_type, typename output_type>
    static std::vector<std::vector<output_type>> cast(const std::vector<std::vector<input_type>> &input)
    {
        std::vector<std::vector<output_type>> output{};
        for(auto &array:input)
        {
            std::vector<output_type> output_array{};
            for(auto &item:array)
            {
                output_array.push_back(static_cast<output_type>(item));
            }
            output.push_back(output_array);
        }
        return output;
    }

    static std::vector<std::vector<double>> ones(int64_t row, int64_t col);
    static bool equal(double a, double b, double epsilon = 1e-9);
    static bool equal(const std::vector<std::vector<double>>& a, 
                    const std::vector<std::vector<double>>& b, 
                    double epsilon = 1e-9);
    static bool equal(const std::vector<double>& a, 
                        const std::vector<double>& b, 
                        double epsilon);
};
